package com.juphoon.oauth.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.juphoon.oauth.core.config.Const;
import com.juphoon.oauth.core.listener.AuthenticationListener;
import com.juphoon.oauth.core.properties.SecurityProperties;
import com.juphoon.oauth.core.utils.CheckMobile;
import com.juphoon.oauth.utils.OAuth2TokenUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;

/**
 * oauth2 认证成功处理handler
 *
 * @author rongbin.huang
 */
@Component("appAuthenticationSuccessHandler")
@Slf4j
public class AppAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {


    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private OAuth2TokenUtils oAuth2TokenUtils;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    @Qualifier("consumerTokenServices")
    private ConsumerTokenServices consumerTokenServices;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private AuthenticationListener authenticationListener;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @SuppressWarnings("unchecked")
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {

        logger.info("登录成功");

        String header = request.getHeader("Authorization");

        if (header == null || !header.startsWith("Basic ")) {
            throw new UnapprovedClientAuthenticationException("请求头中无client信息");
        }

        // 自定义生成jwt token
        String[] tokens = oAuth2TokenUtils.extractAndDecodeHeader(header, request);
        assert tokens.length == 2;

        String clientId = tokens[0];
        String clientSecret = tokens[1];

        ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);
        } else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);
        }


        OAuth2AccessToken token = oAuth2TokenUtils.getAuth2AccessToken(authentication, clientId, clientDetails);

        deviceVail(request.getParameter(securityProperties.getOauth2().getUsernameParameter()), request.getHeader("user-agent"), token.getValue());

        authenticationListener.loginSuccess(request, authentication.getPrincipal(), token.getValue());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(token));

    }


    private void deviceVail(String username, String userAgent, String token) {
        log.info("user-agent {}", userAgent);
        // 当前oauth2 store 为redis时多设备判断
        if (StringUtils.isNotEmpty(userAgent) && Const.OAuth2StoreType.REDIS.equals(securityProperties.getOauth2().getStoreType())) {
            String agent = CheckMobile.getUserAgent(userAgent);
            String phoneToken = redisTemplate.opsForValue().get(username + ":" + CheckMobile.PHONE);
            String pcToken = redisTemplate.opsForValue().get(username + ":" + CheckMobile.PC);
            if (securityProperties.getOauth2().isMultiDevice()) {
                if (StringUtils.isNotEmpty(phoneToken) && CheckMobile.PHONE.equals(agent)) {
                    consumerTokenServices.revokeToken(phoneToken);
                    redisTemplate.delete(username + ":" + CheckMobile.PHONE);
                } else if (StringUtils.isNotEmpty(pcToken) && CheckMobile.PC.equals(agent)) {
                    consumerTokenServices.revokeToken(pcToken);
                    redisTemplate.delete(username + ":" + CheckMobile.PC);
                }
            } else {
                consumerTokenServices.revokeToken(phoneToken);
                consumerTokenServices.revokeToken(pcToken);
                redisTemplate.delete(username + ":" + CheckMobile.PC);
                redisTemplate.delete(username + ":" + CheckMobile.PHONE);
            }
            redisTemplate.opsForValue().set(username + ":" + agent, token, securityProperties.getOauth2().getRefreshTokenValiditySeconds(), TimeUnit.SECONDS);
        }

    }

}
